﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Linq;
using System.Text;

    public class Keyframe
{
    public Keyframe(int bone, TimeSpan time, Matrix transform)
    {
        Bone = bone;
        Time = time;
        Transform = transform;
    }
    private Keyframe()
    {
    }
    [ContentSerializer]
    public int Bone { get; private set; }
    [ContentSerializer]
    public TimeSpan Time { get; private set; }
    [ContentSerializer]
    public Matrix Transform { get; private set; }
}

    public class AnimationClip
    {
        public AnimationClip(TimeSpan duration, List<Keyframe> keyframes)
        {
            Duration = duration;
            Keyframes = keyframes;
        }
        private AnimationClip()
        {
        }
        [ContentSerializer]
        public TimeSpan Duration { get; private set; }
        [ContentSerializer]
        public List<Keyframe> Keyframes { get; private set; }
    }

    public class SkinningData
    {
        public SkinningData(Dictionary<string, AnimationClip> animationClips, List<Matrix> bindPose, List<Matrix> inverseBindPose, List<int> skeletonHierarchy)
        {
            AnimationClips = animationClips;
            BindPose = bindPose;
            InverseBindPose = inverseBindPose;
            SkeletonHierarchy = skeletonHierarchy;
        }
        private SkinningData()
        {
        }
        [ContentSerializer]
        public Dictionary<string, AnimationClip> AnimationClips { get; private set; }
        [ContentSerializer]
        public List<Matrix> BindPose { get; private set; }
        [ContentSerializer]
        public List<Matrix> InverseBindPose { get; private set; }
        [ContentSerializer]
        public List<int> SkeletonHierarchy { get; private set; }
    }

    public class AnimationPlayer
    {
        AnimationClip currentClipValue;
        TimeSpan currentTimeValue;
        int currentKeyframe;
        Matrix[] boneTransforms;
        Matrix[] worldTransforms;
        Matrix[] skinTransforms;
        SkinningData skinningDataValue;
        public AnimationPlayer(SkinningData skinningData)
        {
            if (skinningData == null)
            {
                throw new ArgumentNullException("skinningData");
            }
            skinningDataValue = skinningData;
            boneTransforms = new Matrix[skinningData.BindPose.Count];
            worldTransforms = new Matrix[skinningData.BindPose.Count];
            skinTransforms = new Matrix[skinningData.BindPose.Count];
        }
        public void StartClip(AnimationClip clip)
        {
            if (clip == null)
            {
                throw new ArgumentNullException("clip");
            }
            currentClipValue = clip;
            currentTimeValue = TimeSpan.Zero;
            currentKeyframe = 0;
            //initialize bone transforms to the bind pose
            skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
        }
        public void Update(TimeSpan time, bool relativeToCurrentTime, Matrix rootTransform)
        {
            UpdateBoneTransforms(time, relativeToCurrentTime);
            UpdateWorldTransforms(rootTransform);
            UpdateSkinTransforms();
        }

        private void UpdateWorldTransforms(Matrix rootTransform)
        {
            //root bone
            worldTransforms[0] = boneTransforms[0] * rootTransform;
            //child bones
            for (int bone = 1; bone < worldTransforms.Length; bone++)
            {
                int parentBone = skinningDataValue.SkeletonHierarchy[bone];
                worldTransforms[bone] = boneTransforms[bone] * worldTransforms[parentBone];
            }
        }

        private void UpdateSkinTransforms()
        {
            for (int bone = 0; bone < skinTransforms.Length; bone++)
            {
                skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] * worldTransforms[bone];
            }
        }

        private void UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
        {
            if (currentClipValue == null)
            {
                throw new InvalidOperationException("Startclip");
            }
            //update the animation position
            if (relativeToCurrentTime)
            {
                time += currentTimeValue;
                //if we reached the end, loop back to the start
                while (time >= currentClipValue.Duration)
                {
                    time -= currentClipValue.Duration;
                }
            }
            if ((time < TimeSpan.Zero) || (time >= currentClipValue.Duration))
            {
                throw new ArgumentOutOfRangeException("time");
            }
            //if the position moved backwards, reset the keyframe index
            if (time < currentTimeValue) {
                currentKeyframe = 0;
                skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
            }
            currentTimeValue = time;
            //read keyframe matrices
            IList<Keyframe> keyframes = currentClipValue.Keyframes;
            while (currentKeyframe < keyframes.Count)
            {
                Keyframe keyframe = keyframes[currentKeyframe];
                //stop when we've read up to the current time position
                if (keyframe.Time > currentTimeValue) break;
                //use this keyframe
                boneTransforms[keyframe.Bone] = keyframe.Transform;
                currentKeyframe++;
            }
        }
        public Matrix[] GetBoneTransforms() { return boneTransforms; }
        public Matrix[] GetWorldTransforms() { return worldTransforms; }
        public Matrix[] GetSkinTransforms() { return skinTransforms; }
        public AnimationClip CurrentClip { get { return currentClipValue; } }
        public TimeSpan CurrentTime { get { return currentTimeValue; } }
    }
